// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: daniel

#pragma once

#include <cmath>
#include <sstream>
#include <string>

#include <boost/beast/http/field.hpp>
#include <glog/logging.h>

#include "coin2/base/request/session.h"
#include "nghttp2/includes/nghttp2/asio_http2.h"

namespace coin2::base::request::http2 {

class RequestParser {
  using RequestType = coin2::base::request::Session::RequestType;
  using Http2Header = nghttp2::asio_http2::header_map;
  using Http2Value = nghttp2::asio_http2::header_value;
  using Http2Priority = nghttp2::asio_http2::client::priority_spec;

 public:
  explicit RequestParser(
      bool use_ssl,
      const RequestType& req,
      int32_t weight = 16 /* NGHTTP2_DEFAULT_WEIGHT */)
      : use_ssl_{use_ssl} {
    Parse(req, weight);
    Validate();
  }
  ~RequestParser() = default;
  RequestParser(const RequestParser&) = delete;
  RequestParser& operator=(const RequestParser&) = delete;

  auto Method() const -> const std::string& { return method_; }
  auto Uri() const -> const std::string& { return uri_; }
  auto Payload() const -> const std::string& { return payload_; }
  auto Headers() const -> const Http2Header& { return headers_; }
  auto Priority() const -> const Http2Priority& { return priority_; }

 private:
  void Parse(const RequestType& req, int32_t weight) {
    // 1. method
    method_ = std::string(req.method_string());
    // 2. uri
    auto target = req.target();
    std::stringstream ss;
    ss << (use_ssl_ ? "https://" : "http://") << req.at(boost::beast::http::field::host)
       << (!target.empty() && target[0] == '/' ? "" : "/") << target;
    uri_ = ss.str();
    // 3. payload
    payload_ = req.body();
    // 4. headers
    for (const auto& field : req.base()) {
      auto name = std::string(field.name_string());
      Http2Value value;
      value.value = std::string(field.value());
      value.sensitive = IsSensitiveField(field);
      headers_.emplace(name, value);
    }
    // 5. priority
    // - 1   - NGHTTP2_MIN_WEIGHT
    // - 256 - NGHTTP2_MAX_WEIGHT
    // - 16  - NGHTTP2_DEFAULT_WEIGHT
    auto nghttp2_weight = (int32_t)pow(2, weight);
    priority_ = Http2Priority(0, nghttp2_weight, 0);

    // Print();
  }

  template <typename T>
  bool IsSensitiveField(T&& field) const {
    // TODO(daniel): mark sensitive information
    return false;
  }

  void Print() const {
    auto print_headers = [](const Http2Header& headers) -> std::string {
      std::stringstream ss;
      for (const auto& kv : headers) {
        ss << kv.first << " : " << kv.second.value << "\n";
      }
      return ss.str();
    };
    DLOG(INFO) << "[http2] request:"
               << "\nmethod: " << method_ << "\nuri: " << uri_ << "\nheaders: \n"
               << print_headers(headers_) << "\npayload: \n"
               << payload_;
  }

  void Validate() const {
    // TODO(daniel)
  }

 private:
  const bool use_ssl_ = false;
  std::string method_;
  std::string uri_;
  std::string payload_;
  Http2Header headers_;
  Http2Priority priority_;
};

}  // namespace coin2::base::request::http2
